package office.document.docx4j.utils;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import office.document.core.constant.enums.DocumentHeadLevelEnum;
import office.document.core.domain.DocumentHeadDO;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.*;

import javax.xml.bind.JAXBElement;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * word文档大纲工具类
 *  - 文档大纲解析，获取
 *
 * @author: zhanglu
 * @date: 2023/7/31 17:25
 * @modify: 修改人信息, 修改内容 2023/7/31 17:25
 */
@Slf4j
public class WordOutlineUtil {

    /**
     * 获取大纲
     * @param fileBytes word格式字节数组
     * @return  大纲列表
     */
    public static List<DocumentHeadDO> getHeads(byte[] fileBytes) {
        Assert.isTrue(ObjectUtil.isNotEmpty(fileBytes), "请输入有效的word文件");
        //初始化大纲
        List<DocumentHeadDO> heads = new ArrayList<>();
        try {
            //加载word
            WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new ByteArrayInputStream(fileBytes));
            //获取大纲样式id
            List<Style> styles = wordMLPackage.getMainDocumentPart().getStyleDefinitionsPart().getJaxbElement().getStyle();
            if (ObjectUtil.isEmpty(styles)) {
                return new ArrayList<>();
            }
            //list转为map，便于获取
            Map<String, Style> styleMap = styles.stream().filter(o -> Arrays.stream(DocumentHeadLevelEnum.values())
                            .anyMatch(h -> Objects.equals(h.getCode(), o.getName().getVal())))
                    .collect(Collectors.toMap(Style::getStyleId, Function.identity(), (o, n) -> n));
            //获取文档内容
            List<Object> contents = wordMLPackage.getMainDocumentPart().getContent();
            for (Object o : contents) {
                //文本在P标签里，过滤非P标签的内容
                if (!(o instanceof P)) {
                    continue;
                }
                //获取P标签样式
                P p = (P) o;
                PPrBase.PStyle pStyle = p.getPPr().getPStyle();
                if (ObjectUtil.isEmpty(pStyle)) {
                    continue;
                }
                //获取样式id
                String styleId = pStyle.getVal();
                Style style = styleMap.get(styleId);
                if (ObjectUtil.isEmpty(style)) {
                    continue;
                }
                //获取样式名称，若是大纲，则是大纲名称
                String styleName = style.getName().getVal();
                if (ObjectUtil.isEmpty(style) || ObjectUtil.isEmpty(styleName)) {
                    continue;
                }
                //根据大纲名称获取大纲等级
                DocumentHeadLevelEnum headLevelEnum = DocumentHeadLevelEnum.getByCode(styleName);
                if (ObjectUtil.isEmpty(headLevelEnum)) {
                    continue;
                }
                //获取大纲文本
                String text = "";
                //大纲文本段落
                List<Object> pcs = p.getContent();
                //文本都在P标签的R标签的T标签里
                for (Object pc : pcs) {
                    if (!(pc instanceof R)) {
                        continue;
                    }
                    //获取R标签
                    R r = (R) pc;
                    List<Object> rcs = r.getContent();
                    for (Object rc : rcs) {
                        if (!(rc instanceof JAXBElement) || !(((JAXBElement<?>) rc).getValue() instanceof Text)) {
                            continue;
                        }
                        //获取T标签，即大纲内容
                        Text t = (Text) ((JAXBElement<?>) rc).getValue();
                        text += t.getValue();
                    }
                }
                DocumentHeadDO head = new DocumentHeadDO();
                head.setId(style.getName().getVal());
                head.setHeadLevel(headLevelEnum);
                head.setText(text);
                heads.add(head);
            }
        } catch (Exception e) {
            log.error("获取word大纲异常", e);
        }
        return heads;
    }

    /**
     * 根据大纲列列表，构建大纲树
     * @param heads
     * @return
     */
    public static DocumentHeadDO buildHeadTree(List<DocumentHeadDO> heads) {
        DocumentHeadDO root = heads.get(0);
        //检查大纲层级，不宜过多，加限制，保护程序
        checkHead(root, heads);
        //构建树形
        buildHead(0, root.getHeadLevel().getIndex(), root, heads);
        return root;
    }

    /**
     * 检查大纲层级
     *  - 不宜过多，加限制，保护程序
     * @param root      根节点
     * @param sources   大纲列表
     */
    private static void checkHead(DocumentHeadDO root, List<DocumentHeadDO> sources){
        Assert.isTrue(ObjectUtil.isNotEmpty(root) && ObjectUtil.isNotEmpty(sources));
        Assert.isTrue(sources.stream().allMatch(o -> Objects.equals(root, o) || o.getHeadLevel().getIndex() > root.getHeadLevel().getIndex()));
    }

    /**
     * 构建树形
     * @param count     递归次数，限制保护程序
     * @param index     当前索引
     * @param root      根节点
     * @param sources   大纲列表
     */
    private static void buildHead(Integer count, Integer index, DocumentHeadDO root, List<DocumentHeadDO> sources){
        Assert.isTrue(count <= sources.size(), "提取标题异常，子标题过多:{}", count);
        if(ObjectUtil.isEmpty(index) || index >= sources.size()){
            return;
        }
        //找兄弟
        DocumentHeadDO nextHead = sources.get(index);
        //下一个节点层级等于当前节点层级，则结束
        if(nextHead.getHeadLevel().getIndex() <= root.getHeadLevel().getIndex()){
            return;
        }
        //判断下一个节点是否是当前节点的孩子
        DocumentHeadDO currentHead = getHeadChilds(sources.size(), root, nextHead);
        if(ObjectUtil.isEmpty(currentHead)){
            return;
        }
        currentHead.getChilds().add(nextHead);
        count ++;
        buildHead(count, index + 1, root, sources);
    }

    /**
     * 找孩子
     * @param count     递归次数，限制保护程序
     * @param head      根节点
     * @param nextHead  下一级节点
     * @return
     */
    public static DocumentHeadDO getHeadChilds(Integer count, DocumentHeadDO head, DocumentHeadDO nextHead){
        Assert.isTrue(count >= 0, "提取标题异常，子标题过多:{}", count);
        if(ObjectUtil.isEmpty(head.getChilds())){
            //没有孩子了，则返回
            head.setChilds(new ArrayList<>());
            return head;
        }
        for (DocumentHeadDO child : head.getChilds()) {
            count--;
            if(child.getHeadLevel().getIndex() == nextHead.getHeadLevel().getIndex()){
                //根节点的孩子层级等于下一个节点的层级，说明下一个节点就是当前节点的孩子
                return head;
            } else if(child.getHeadLevel().getIndex() < nextHead.getHeadLevel().getIndex()){
                //根节点的孩子层级小于下一个节点的层级，说明还没找完
                return getHeadChilds(count, head.getChilds().get(head.getChilds().size() - 1), nextHead);
            } else if(child.getHeadLevel().getIndex() > nextHead.getHeadLevel().getIndex()){

            }
        }
        return null;
    }

}
